המשימה: לשלוף כמה שורות מטבלת מסד נתונים באקראיות.
הפתרון: SELECT * FROM `table` ORDER BY RAND() LIMIT 1
התוצאה: נפל השרת. למה, מה קרה, ואיך היה צריך לעשות זאת?
בפני מפתחים רבים עומדת המשימה לשלוף כמה שורות מטבלה באקראיות. הדרך הנפוצה ביותר היא השאילתה הבאה:
SELECT * FROM `table` ORDER BY RAND() LIMIT 1
השאילתה הזו מסוכנת מאוד בטבלאות גדולות, לא יעילה ועשויה לתקוע את השרת לזמן מה.
הֶסבר השיטה הנפוצה – ORDER BY rand()
הפונקציה rand מחזירה מספר רנדומלי בין אפס לאחד. כאשר משתמשים בה ב-Order By:
1. המסד עובר על כל שורה בטבלה ומחשב מספר רנדומלי (בין 0 ל-1).
2. הוא רושם אותו עבור אותה שורה בעמודה זמנית.
3. הוא ממיין את השורות לפי העמודה הזו.
4. מהטבלה החדשה והממוינת שנמצאת בזיכרון נשלפות השורות שהתבקשו.
כלומר – שליפה מאוד כבדה
כדי למיין לפי מספר רנדומלי המסד חייב לעבור על כל השורות בטבלה ולחשב עבורן מספר רנדומלי. אחר כך המסד שומר בזיכרון טבלה חדשה עם מספרים, ולבסוף עליו למיין אותה ולשלוף ממנה שורה אחת.
תחשבו כמה עומס יהיה על המסד אם הפעולה הזו תתבצע עבור כל גולש באתר... ריבוי פעולות שכזה יורגש טוב מאוד לרעה.
אלטרנטיבה לטבלאות גדולות
האפשרות החלופית היא שימוש ב-LIMIT. השתמשנו בה עבור חלוקת פלט לעמודים, זוכרים?
LIMIT מקבלת שני ארגומנטים: הראשון הוא מספר השורה שממנה יש להתחיל את החיפוש והשני הוא מספר השורות שיש להחזיר.
את הארגומנט הראשון אפשר לנצל לצורך המשימה שלנו. במקום ש-LIMIT תתחיל את השליפה מהשורה הראשונה, נאמר לה שתשלוף בכל פעם שורה אחת ממיקום שרירותי כלשהו בטבלה – קודם כל לשלוף שורה אחת החל מהשורה השנייה, אחר-כך החל מהשורה השבע-עשרה ובסוף החל מהשורה התשיעית.
SELECT * FROM `table` LIMIT 2,1
SELECT * FROM `table` LIMIT 17,1
SELECT * FROM `table` LIMIT X,1
SELECT * FROM `table` LIMIT 17,1
SELECT * FROM `table` LIMIT X,1
בשיטה הזו יש צורך לחשב מספר אקראי אחד בלבד, במקום לחשב מספר אקראי עבור כל שורה בטבלה וליצור טבלה זמנית. אבל עכשיו בעיה אחרת ניצבת לפנינו: המספר האקראי שיוגרל חייב להיות קטן ממספר השורות בטבלה.
לכאן מצטרפת שליפה נוספת שתספור את כמות השורות בטבלה:
SELECT COUNT(*) FROM `table`
השליפה הזו תעזור לנו לבחור מספר אקראי בין 0 לכמות השורות בטבלה, ואותו נעביר ל-Limit. כדי להגריל מספר אקראי כזה נשתמש ב-Rand() עם השאילתה הבאה, שתחזיר מספר אקראי בין אפס לכמות השורות שבטבלה:
SELECT FLOOR( RAND() * COUNT(*))
את המספר שנקבל נוכל להציב ב-Limit:
$rand_q = mysql_query("SELECT FLOOR( RAND() * COUNT(*) ) FROM `table` ");
$rand = mysql_result($rand_q ,0,0);
$result_q = mysql_query("SELECT * FROM `table` LIMIT $rand,1");
$rand = mysql_result($rand_q ,0,0);
$result_q = mysql_query("SELECT * FROM `table` LIMIT $rand,1");
לצערי, MySQL לא מאפשרת להציב תת-שליפה כפרמטר ב-Limit, ולכן הקוד הבא לא עובד:
SELECT *
FROM `table`
LIMIT (
SELECT FLOOR( RAND( ) * COUNT( * ) )
FROM `table`
), 1
FROM `table`
LIMIT (
SELECT FLOOR( RAND( ) * COUNT( * ) )
FROM `table`
), 1
החסרונות בשיטה הזאת
1. השיטה הזו פחות טובה במקרה שאתם רוצים לשלוף יותר משורה אחת בו זמנית, כיוון ש-Limit תמיד יחזיר שורות עוקבות (אחת אחרי השנייה). אם זו תהיה טבלה ממש גדולה כנראה לא ממש יבחינו בזה, אבל בכל זאת השליפה לא ממש אקראית.
אם זה עקרוני וחשוב, אזי ניתן להריץ את אותה שאילתה כמה פעמים ולקבל כמה תוצאות שונות, אבל יש לשמור על כך שלא ייבחרו השורות שכבר נבחרו בעבר.
2. יש לזכור תמיד לשנות את השאילתה בשני מקומות אם צריכים לבצע איזשהו שינוי: פעם אחת בשאילתה עצמה, ופעם נוספת בשאילתה שסופרת שורות, אחרת התוצאות יהיו בלתי צפויות.
למרות שיש כאן שתי שליפות, הדרך הזו הרבה יותר יעילה ומהירה מ-Order By Rand(), בייחוד בטבלאות גדולות עם אלפי שורות. בטבלאות קטנות (עד מאה שורות) לא מרוויחים מכך, ואין צורך ב-Limit ובשתי שליפות.
תגובות לכתבה:
במה עוזרת שליפה רנדומלית?
תודה.
היא לא עוזרת בשום דבר, באיחוד כשאתה לא צריך אותה.
כשאתה צריך אותה לדברים כמו להציג פרסומת רנדומלית, מוצר כלשהו בעמוד הראשי, בדיחה שונה כל פעם ממאגר בדיחות —
יש לך את הדרך המפורטת מעלה לבצע את השליפה.
אני אשתמש בבזה במערכת שלי..
תודה אלכס..
תודה על המאמר רק דבר קטן..
אני לא השתמשתי בRAND בתוך השאילתה אלא ספרתי את השורות שיש במסד והכנסתי לתוך פקודת RAND ב PHP כך:
rand(1,$rand)
תודה רבה מאמר מעולה!
אבי,מה יקרה אם ערך אחד ימחק ובמקרה תצא הID של הערך המחוק?
בקוד שהבאת בשביל לפתור את העומס בשאילתה - יוצא שאם תשלוף 3 שורות אז הם לא יהיו רנדומלי כשלעצמם, אלא כסדר שהם מופיעות בטבלה (כך גם אמרת בעצמך).
אז אולי יהיה אפשר להוסיף RAND גם ל ORDER BY, ואז יהיה בשאילתה 2 RAND - גם ללימיט (מאיפה שהוא יתחיל את השליפה) בשביל לחסוך בעומס השאילתה,
וגם יהיה RAND בORDER BY - בשביל שה3 השורות ישלפו בצורה רנדומלית.
??? מה אתה אומר ???
אני אומר שאתה צודק לגמרי ובמקרה הזה יהיה יותר אפקטיבי לעשות שלוש שליפות נפרדות.
למרות שזה מאוד תלוי בכמות השורות שיש לך במסד.
הבעיה אם איך שתיארת את הרעיון זה ש
או שהרנדום יחושב רק לשלוש תמונות שעומדות ברצף, ואש תקבל את אותם שלוש תמונות רצופות בסדר שונה
או שהרנדום יחושב לכל שורה בטלבה ובעצם לא הרווחת הרבה
ניסיתי לעשות 2 רנדום (גם לאורדר ביי וגם ללימיט) והוא באמת מביא לי רנדומלי לגמרי (ולא כמו שחשבת שהרנדום יהיה רק מתוצאות הלימיט)
השאלה האם זה באמת יותר טוב, או כמו שאמרת שזה עדיין מחשב לכל הטבלה? מה זה עושה איך אני ידע?
לא, אם אתה עושה ORDER BY אז מחושב ערך רנדומלי עבור כל שורה ואז הרנדום ללימיט מיותר.
אז עלה לי רעיון אחר:
1. לספור את כל השורות שיש בטבלה שבמסד(COUNT)
2. להשתמש בפונקצייה RAND בPHP ושתתחיל מהמספר 1 עד המספר - התוצאה של ספירת השורות שלעיל.
3. להריץ את הRAND בתוך לולאה ל10 סיבובים ולהכניס את המספר הרנדומלי למערך (ובסוף ייצא מערך עם 10 מפתחות).
4. לעשות שאילתת SQL שתשלוף את כל השורות שהID שווה ל"מערך[0]" AND "מערך[1]"...
(פשוט להשתמש בשאילתה בWHERE..)
איך זה יעיל יותר? רעיון טוב?
זה מעולה וזה בדיוק מה שצריך לעשות :)
>>>זה מעולה...
כי בכתבה לא רשמת ככה..אלא דרך אחרת(בשאילתה SQL הגרלת מספר רנדומלי והכנסת אותו ללימיט של שאילה נוספת), למה?
שאלה נוספת: לפי הרעיון שלי (תגובה קודמת) אז בשאילתה יש WHERE ID = $ARRAY[0] X..(אל תתיחס לX -הוא שם, רק כדי לסדר את השאילתה בשביל הטקסטרה)
האם צריך לעשות בחלק של הAND שוב פעם את ID AND = למערך[1], ואז שוב את ID AND = למערך[2]?
או שאפשר לעשות בAND כך: ID= ARRAY[1] AND ARRAY[2] X
בקיצור: האם לאחר כל AND צריך לרשום שוב את הID שווה ל.. או שמספיק לרשום אותו רק בפעם הראשונה ואז בשאר הAND מספיק רק: שווה ל1,שווה ל2,שווה ל3...
כי אם אני שולף 10 שורות אז השאילתה תהיה יותר ארוכה אם בכל AND יהיה את ID = XXX
זה היה מובן?
משתמש_156408
אתה מתכוון OR ולא AND, איך עמודה מסוימת תהיה שווה לשני ערכים בו-זמנית?
אתה יכול להשתמש בפונקציה IN.
ולמעשה זה:
'WHERE `column`='1' OR `column`='2
שווה לזה:
('WHERE `column` IN('1','2
וכמובן שאתה יכול לשלוח עוד פרמטרים ל-IN.
תודה על ה IN וה OR..
ומה התכוונת ב"עוד פרמטרים"? ( - אפשר עוד תנאים בתוך הסוגריים?)
>> כי בכתבה לא רשמת ככה..אלא דרך אחרת(בשאילתה SQL הגרלת מספר רנדומלי והכנסת אותו ללימיט של שאילה נוספת), למה?
בכתבה שלפני שורה אחת בלבד. במקרה הזה אין טעם לגרום ל PHP לעשות שני שאילתות נפרדות למסד נתונים ולהגריל מספר, אם אפשר לעשות הכל בשאילתה אחת.
האמת שהרגע קראתי שוב את התגובה הקודמת שלך ואי אפשר לעשות את זה.
אם בטבלה יש 3 שורות עם id = 1, id = 2 & id = 5
אתה יכול להגיר שני מספרים בין אחד לשלוש ותקבל שגיאה בשאילתה.
אתה יכול לכתוב פרוצדורה למסד שפעם אחת תשלוף את מספר השורות במסד ולאחר מכן תבצע X שאילתות בלולאה עם limit @rand, 1
ע"פ כל הדיון כאן, כתבתי קוד ותגידו לי האם כך באמת שולפים 6 שורות רנדומליות מהמסד בצורה הכי יעילה.
http://phpguide.co.il/phplive?code=349
^
ממש לא יעיל. זה רחוק מיעיל.
אני מבולבל, יותר מיומיים אני יושב על ה"שליפה היעילה", עד שהגעתי לקוד למעלה שאלכס אמר שזה בסדר גמור, ועכשיו אתה אומר שלא.
בכל מקרה: אשמח לשמוע איך כן יעיל לעשות את זה..תודה.
בשביל מסד עם כמות מידע לא רב זה בסדר. אבל עדיין, יש הרבה מה לשפר.
- fetch_array בשביל מה להוציא מערך כפול? אם כבר, אז לך על fetch_row
- זימון של 6 שאילתות מיותרות - אפשר היה לעשות את זה בשאילתה אחת
- אין בדיקה של כפילויות
- עוד פעם הוצאה עם array
בקיצור, קוד רע.
>>>fetch_array בשביל מה להוציא מערך כפול? אם כבר, אז לך על fetch_row
- האמת שהשתמשתי ב fetch_array עם MYSQL_NUM. ואז הוא שולף רק מערך מספרי, אבל עדיין תודה אני ישתמש row יותר קצר
>>>זימון של 6 שאילתות מיותרות - אפשר היה לעשות את זה בשאילתה אחת
>>> אין בדיקה של כפילויות
- אשמח לשמוע איך לעשות את השאילתה ואת הבדיקה כנגד כפילויות.
>>>- עוד פעם הוצאה עם array
- כמו הראשון..